feat(server): expose the query engine to agents over MCP#21
Conversation
Layer a Model Context Protocol tool surface on the existing Spring Boot gateway using Spring AI 2.0.0 (WebMvc Streamable-HTTP, SYNC), so an agent can discover tables, inspect schemas, preview data and query cost, and run read-only SQL without writing SQL blind — and without bypassing the server's budget, concurrency, and audit guarantees. Engine module untouched; Java 17. - add CuckooMcpTools (@component, five @mcptool methods: list_tables, describe_table, sample_rows, explain_query, query). Every query tool routes through QueryService.execute(...,"mcp") and every catalog tool through CatalogFacade, so MCP traffic inherits budget clamping, the concurrency permit, and the audit trail with no MCP-specific governance code - mirror the REST errorCode taxonomy onto CallToolResult.isError(true): engine errors are agent-legible and verbatim, while DATA_ERROR/INTERNAL are scrubbed to a generic message + correlation id (full detail logged server-side only) - extract the table-name charset guard (TableNameValidator) and the catalog-column -> DTO mapping (CatalogMapper) into the catalog package and delegate TableController to them, so the REST and MCP surfaces share one validation and one schema shape - import spring-ai-bom 2.0.0 + spring-ai-starter-mcp-server-webmvc (no reparent; the starter pulls the transport transitively) and configure the SYNC Streamable-HTTP server at /mcp (SSE is deprecated/broken on Boot 4 + Tomcat 11) - the bare @component is auto-registered by the annotation scanner; no ToolCallbackProvider bean (the legacy @tool path) is declared Verify with ./mvnw clean verify: engine 419 and server 90 passing tests, 0 failures; the 20 sample queries are byte-identical. The new tests cover the five tools at three tiers — plain JUnit, Mockito, and a @SpringBootTest that both drives the tools directly (choke-point routing, budget inheritance) and round-trips a real MCP client over the live /mcp endpoint on Tomcat 11. Known follow-up: tools pass the constant "mcp" principal (per-key MCP auth returns with Phase-4 governance), and a table-upload tool is deferred (it needs a write-path extraction). MCP schema generation relies on -parameters, so a clean build is required for correct tool argument names.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@claude review this PR |
|
Claude finished @JinBa1's task in 2m 26s —— View job PR Review: feat(server): expose the query engine to agents over MCPTasks:
SummaryThis is a well-designed, well-tested MCP layer over the Spring Boot gateway. The architecture decisions are sound, the security posture is careful, and the test coverage is thorough (unit, mock, integration, live wire). A few observations below — none are blockers, but two are worth considering before this goes into production. ArchitectureChoke-point routing ( DRY extraction of Error model ( SecuritySQL injection via TableNameValidator.validate(name); // blocks [^A-Za-z0-9_] and length > 64
int n = clampLimit(limit); // int, so safe
String sql = "SELECT * FROM " + name + " LIMIT " + n;The guard is structurally sound. No read-only enforcement at the tool layer for // in query() and explainQuery(), before calling queryService:
String upper = sql.strip().toUpperCase();
if (!upper.startsWith("SELECT") && !upper.startsWith("WITH") && !upper.startsWith("(")) {
return error("BAD_REQUEST: Only SELECT statements are permitted.");
}This isn't required given the engine's behavior, but it would make the safety guarantee explicit and agent-legible without an engine roundtrip. Fix this → Correctness
private CallToolResult json(Object payload) {
return CallToolResult.builder()
.addTextContent(objectMapper.writeValueAsString(payload))
.build();
}If
TestsCoverage is comprehensive across three tiers:
One missing test: Configuration
The BOM import strategy in Nitpicks
VerdictApprove with one suggestion: The missing read-only guard at the tool layer for |
|
Thanks for the review. On the one suggestion — the tool-layer read-only guard for
Net: it adds a fragile, surface-inconsistent check that breaks a tested behavior to guard a non-threat. Leaving the engine's parse-based guarantee as the belt. The |
Layers a Model Context Protocol tool surface on the Spring Boot gateway using Spring AI 2.0.0 (WebMvc Streamable-HTTP, SYNC), completing the "REST + Spring Boot + MCP for agents" surface. The engine module is untouched and Java stays 17; the entire change lives in
server/.Tools (all read-only)
list_tablesdescribe_tablesample_rowsexplain_queryqueryDesign
QueryService.execute(...,"mcp")and every catalog tool callsCatalogFacade; no tool touches the engine directly. MCP traffic therefore inherits budget clamping, the concurrency permit, and the audit trail for free — and per-key governance, when it lands insideQueryService, is inherited with no tool-signature change.errorCodetaxonomy ontoCallToolResult.isError(true): engine errors are agent-legible and verbatim;DATA_ERROR/INTERNALare scrubbed to a generic message + correlation id (full detail logged server-side only), exactly as the REST 5xx path.catalogpackage;TableControllerdelegates to them, so REST and MCP share one validation and one schema shape (no drift).spring-ai-bom:2.0.0+spring-ai-starter-mcp-server-webmvc(non-reparent BOM; transport pulled transitively). The bare@Componentis auto-registered by the annotation scanner — noToolCallbackProviderbean. SYNC Streamable-HTTP at/mcp(SSE is deprecated/broken on Boot 4 + Tomcat 11).Verification
./mvnw clean verify: engine 419 + server 90 passing, 0 failures; the 20 sample queries are byte-identical. New tests span three tiers — plain JUnit, Mockito, and a@SpringBootTestthat both drives the tools directly (choke-point routing, budget inheritance proven by a tight per-call budget) and round-trips a real MCP client over the live/mcpendpoint on Tomcat 11.Notes / deferrals
"mcp"principal, which the audit trail already records.-parametersflag, so a clean build is required (CI runsclean).